#include "flash_W25Q64JV.h"
#include "spi.h"
#include "gpio.h"
#include "cmsis_os2.h"
#include "FreeRTOS.h"
#include "task.h"
#include "log.h"
#include "semphr.h"

static TaskHandle_t s_task_hW25Q64JV;

#define CFG_SECTION_START_ADDR 	0x7FF000
#define CFG_PAGE_START_ADDR 		0x7FFA00

#ifdef W25Q64JV_USE_DMA
static SemaphoreHandle_t s_sema_W25Q64JV;
#endif

static void vTaskCode_W25Q64JV(void * arg)
{
	int ret = 0;
	uint8_t data_buf[32] = {0};
	
	ret = W25Q64JV_ReadData(CFG_PAGE_START_ADDR, data_buf, sizeof(data_buf));
	for (int i = 0; i < sizeof(data_buf); i++)
	{
		my_log("data_buf[%d] 0x%02x", i, data_buf[i]);
		
		if(data_buf[i] == 0xFF)
		{
			data_buf[i] = i * i;
		}
		else
		{
			data_buf[i] += 1;
		}
		
		my_log(", next 0x%02x\n", data_buf[i]);
	}
	
	//vTaskDelay(1000);
	W25Q64JV_EraseSector(CFG_SECTION_START_ADDR);
	ret = W25Q64JV_PageProgram(CFG_PAGE_START_ADDR, data_buf, sizeof(data_buf));
	my_log("page program ret %d\n", ret);

	while(1)
	{
		vTaskDelay(5000);
		continue;
		//HAL_Delay(1000);
		
		uint8_t fls_st_reg_1;
		ret = W25Q64JV_Read_StatusRegister1(&fls_st_reg_1);
		
		my_log("ret %d, busy %d, write enabled %d\r\n", ret, W25Q64JV_IS_BUSY(fls_st_reg_1), W25Q64JV_IS_WRITE_ENABLED(fls_st_reg_1));
		//vTaskDelay(100);
		//HAL_Delay(1000);
		
		uint8_t mid, did;
		ret = W25Q64JV_Read_ManufacturerIDAndDeviceID(&mid, &did);
		my_log("ret %d, mid %x, did %x\r\n", ret, mid, did);
	}
	
	vTaskDelete(NULL);
}

int W25Q64JV_init(void)
{
#ifdef W25Q64JV_USE_DMA
	s_sema_W25Q64JV = xSemaphoreCreateBinary();
#endif
	
	BaseType_t ret = xTaskCreate(vTaskCode_W25Q64JV, "W25Q64JV", 256, NULL, osPriorityNormal, &s_task_hW25Q64JV);
	
	return 0;
}

#ifdef W25Q64JV_USE_DMA

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
	if(hspi != &hspi1)
	{
		return;
	}
	
	BaseType_t bHigherPriTaskWoken;
	xSemaphoreGiveFromISR(s_sema_W25Q64JV, &bHigherPriTaskWoken);
	
	portYIELD_FROM_ISR(bHigherPriTaskWoken);
}

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
	if(hspi != &hspi1)
	{
		return;
	}
	
	BaseType_t bHigherPriTaskWoken;
	xSemaphoreGiveFromISR(s_sema_W25Q64JV, &bHigherPriTaskWoken);
	
	portYIELD_FROM_ISR(bHigherPriTaskWoken);
}

void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
	if(hspi != &hspi1)
	{
		return;
	}
	
	BaseType_t bHigherPriTaskWoken;
	xSemaphoreGiveFromISR(s_sema_W25Q64JV, &bHigherPriTaskWoken);
	
	portYIELD_FROM_ISR(bHigherPriTaskWoken);
}

static void chip_select()
{
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
}

static void chip_unselect()
{
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
}

static int send_cmd_recv_result(uint8_t *cmd_buf, int cmd_len, uint8_t *result_buf, int result_buf_size, int timeout_ms)
{
	chip_select();
	
	HAL_SPI_Transmit_DMA(&hspi1, cmd_buf, cmd_len);
	
	xSemaphoreTake(s_sema_W25Q64JV, 1000);
	
	if(result_buf_size > 0)
	{
		HAL_SPI_Receive_DMA(&hspi1, result_buf, result_buf_size);
	
		xSemaphoreTake(s_sema_W25Q64JV, 1000);
	}
	
	chip_unselect();
	
	return 0;
}

static int send_cmd_2(uint8_t *cmd_buf, int cmd_len, uint8_t *cmd_buf_2, int cmd_len_2, int timeout_ms)
{
	chip_select();
	
	HAL_SPI_Transmit_DMA(&hspi1, cmd_buf, cmd_len);
	
	xSemaphoreTake(s_sema_W25Q64JV, 1000);
	
	if(cmd_len_2 > 0)
	{
		HAL_SPI_Transmit_DMA(&hspi1, cmd_buf_2, cmd_len_2);
	
		xSemaphoreTake(s_sema_W25Q64JV, 1000);
	}
	
	chip_unselect();
	
	return 0;
}

#else
static int send_cmd_recv_result(uint8_t *cmd_buf, int cmd_len, uint8_t *result_buf, int result_buf_size, int timeout_ms)
{
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
	
	HAL_SPI_Transmit(&hspi1, cmd_buf, cmd_len, timeout_ms);
	
	HAL_SPI_Receive(&hspi1, result_buf, result_buf_size, timeout_ms);
	
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
	
	return 0;
}
#endif

int W25Q64JV_Read_StatusRegister1(uint8_t *reg)
{
    if(!reg)
		{
			return -1;
		}
		
		uint8_t cmd = 0x05;
		
		return send_cmd_recv_result(&cmd, 1, reg, 1, 1000);
}

int W25Q64JV_Read_ManufacturerIDAndDeviceID(uint8_t *mid, uint8_t *did)
{
	if(!mid || !did)
	{
		return -1;
	}
	
	uint8_t cmd_buf[4] = {0x90};
	uint8_t result_buf[2];
	
	int ret = send_cmd_recv_result(cmd_buf, sizeof(cmd_buf), result_buf, sizeof(result_buf), 1000);
	if(0 == ret)
	{
		*mid = result_buf[0];
		*did = result_buf[1];
	}
	
	return ret;
}

static int write_enable()
{
	uint8_t cmd = 0x06;
	
	send_cmd_recv_result(&cmd, sizeof(cmd), NULL, 0, 1000);
	
	return 0;
}

int W25Q64JV_EraseSector(uint32_t dwSecStartAddr)
{
	my_log("erase sector: 0x%x\n", dwSecStartAddr);
	
	write_enable();
	
	uint8_t statreg1;
	do
	{
		W25Q64JV_Read_StatusRegister1(&statreg1);
		my_log("erase sector: write enabled %d\n", W25Q64JV_IS_WRITE_ENABLED(statreg1));
		
		vTaskDelay(100);
	} while(!W25Q64JV_IS_WRITE_ENABLED(statreg1));
	
	uint8_t cmd[4] = {0x20};
	cmd[1] = (dwSecStartAddr >> 16) & 0x7F;
	cmd[2] = (dwSecStartAddr >> 8) & 0xFF;
	cmd[3] = (dwSecStartAddr) & 0xFF;
	send_cmd_recv_result(cmd, sizeof(cmd), NULL, 0, 1000);
	
	do
	{
		vTaskDelay(100);
		
		W25Q64JV_Read_StatusRegister1(&statreg1);
		my_log("erase sector: busy %d\n", W25Q64JV_IS_BUSY(statreg1));
	} while(W25Q64JV_IS_BUSY(statreg1));
	
	return 0;
}

int W25Q64JV_PageProgram(uint32_t dwStartAddr, uint8_t *data_buf, uint32_t data_len)
{
	uint8_t statreg1;
	
	my_log("page program: 0x%x\n", dwStartAddr);
	
	write_enable();
	
	uint8_t cmd[4] = {0x02};
	cmd[1] = (dwStartAddr >> 16) & 0x7F;
	cmd[2] = (dwStartAddr >> 8) & 0xFF;
	cmd[3] = (dwStartAddr) & 0xFF;
	send_cmd_2(cmd, sizeof(cmd), data_buf, data_len, 0);
	
	do
	{
		vTaskDelay(100);
		
		W25Q64JV_Read_StatusRegister1(&statreg1);
		my_log("page program: busy %d\n", W25Q64JV_IS_BUSY(statreg1));
	} while(W25Q64JV_IS_BUSY(statreg1));
	
	return 0;
}

int W25Q64JV_ReadData(uint32_t dwStartAddr, uint8_t *data_buf, int32_t data_len)
{
	uint8_t cmd[4] = {0x03};
	cmd[1] = (dwStartAddr >> 16) & 0x7F;
	cmd[2] = (dwStartAddr >> 8) & 0xFF;
	cmd[3] = dwStartAddr & 0xFF;
	
	int ret = send_cmd_recv_result(cmd, sizeof(cmd), data_buf, data_len, 1000);
	
	return ret;
}

int W25Q64JV_FastReadData(uint32_t dwStartAddr, uint8_t *data_buf, int32_t data_len)
{
	uint8_t cmd[5] = {0x0B};
	cmd[1] = (dwStartAddr >> 16) & 0x7F;
	cmd[2] = (dwStartAddr >> 8) & 0xFF;
	cmd[3] = dwStartAddr & 0xFF;
	cmd[4] = 0x0;
	
	int ret = send_cmd_recv_result(cmd, sizeof(cmd), data_buf, data_len, 1000);
	
	return ret;
}
